package com.zeny.express.framework.core.service.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.ExecutorException;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.mapping.ParameterMode;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.property.PropertyTokenizer;
import org.apache.ibatis.scripting.xmltags.ForEachSqlNode;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.type.TypeHandler;
import org.apache.ibatis.type.TypeHandlerRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.zeny.express.framework.core.domain.Page;
import com.zeny.express.framework.core.service.PaginationService;

public class PaginationServiceImpl implements
		PaginationService {

	private static final Logger LOGGER = LoggerFactory.getLogger(PaginationServiceImpl.class);
	
	/**
	 * 数据合计汇总
	 * 
	 * @author zhuwei
	 * @date 2013年10月17日 下午9:02:57
	 * @see com.zeny.express.framework.core.service.PaginationService#getCount(java.lang.String,
	 *      org.apache.ibatis.mapping.MappedStatement,
	 *      org.apache.ibatis.mapping.BoundSql,
	 *      com.zeny.express.framework.core.domain.Page)
	 */
	@Override
	public Map<String, Object> getCount(String key, MappedStatement ms,
			BoundSql boundSql, Page<?> page) throws SQLException {
		return this.getCount(ms, boundSql, page);
	}

	/**
	 * 数据合计汇总
	 * 
	 * @author zhuwei
	 * @date 2013年10月17日 下午9:02:57
	 * @see com.zeny.express.framework.core.service.PaginationService#getCount(java.lang.String,
	 *      org.apache.ibatis.mapping.MappedStatement,
	 *      org.apache.ibatis.mapping.BoundSql,
	 *      com.zeny.express.framework.core.domain.Page)
	 */
	@Override
	public Map<String, Object> getCount(MappedStatement ms, BoundSql boundSql,
			Page<?> page) throws SQLException {
		Connection connection = ms.getConfiguration().getEnvironment()
				.getDataSource().getConnection();
		Map<String, Object> result = getCount(page.getAggregationMap(),
				boundSql.getSql().trim(), connection, ms,
				boundSql.getParameterObject(), boundSql);

		return result;
	}

	/**
	 * 查询总纪录数
	 * 
	 * @param sql
	 *            SQL语句
	 * @param connection
	 *            数据库连接
	 * @param mappedStatement
	 *            mapped
	 * @param parameterObject
	 *            参数
	 * @param boundSql
	 *            boundSql
	 * @return 总记录数
	 * @throws SQLException
	 *             sql查询错误
	 */
	private Map<String, Object> getCount(
			final Map<String, String> aggregationMap, final String sql,
			final Connection connection, final MappedStatement mappedStatement,
			final Object parameterObject, final BoundSql boundSql)
			throws SQLException {

		StringBuilder countSql = new StringBuilder();
		countSql.append("select count(1) " + Page.COUNT_ALIAS);
		for (Map.Entry<String, String> map : aggregationMap.entrySet()) {
			countSql.append(",").append(map.getValue()).append(" ")
					.append(map.getKey());
		}
		countSql.append(" from (").append(sql)
				.append(") as PAGINATION_AGGREGATION");
		LOGGER.trace(countSql.toString());

		PreparedStatement countStmt = null;
		ResultSet rs = null;
		try {
			countStmt = connection.prepareStatement(countSql.toString());
			BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(),
					countSql.toString(), boundSql.getParameterMappings(),
					parameterObject);
			setParameters(countStmt, mappedStatement, countBS, parameterObject);
			rs = countStmt.executeQuery();

			Map<String, Object> map = new HashMap<String, Object>();

			if (rs.next()) {
				map.put(Page.COUNT_ALIAS, rs.getInt(Page.COUNT_ALIAS));

				for (Map.Entry<String, String> entry : aggregationMap
						.entrySet()) {
					String key = entry.getKey();
					Object value = rs.getObject(key);
					map.put(key, value);
				}
			}

			return map;
		} finally {
			if (rs != null) {
				rs.close();
			}
			if (countStmt != null) {
				countStmt.close();
			}
			if (connection != null) {
				connection.close();
			}
		}
	}

	/**
	 * 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.
	 * DefaultParameterHandler
	 * 
	 * @param ps
	 *            表示预编译的 SQL 语句的对象。
	 * @param mappedStatement
	 *            MappedStatement
	 * @param boundSql
	 *            SQL
	 * @param parameterObject
	 *            参数对象
	 * @throws java.sql.SQLException
	 *             数据库异常
	 */
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private void setParameters(PreparedStatement ps,
			MappedStatement mappedStatement, BoundSql boundSql,
			Object parameterObject) throws SQLException {
		ErrorContext.instance().activity("setting parameters")
				.object(mappedStatement.getParameterMap().getId());
		List<ParameterMapping> parameterMappings = boundSql
				.getParameterMappings();
		if (parameterMappings != null) {
			Configuration configuration = mappedStatement.getConfiguration();
			TypeHandlerRegistry typeHandlerRegistry = configuration
					.getTypeHandlerRegistry();
			MetaObject metaObject = parameterObject == null ? null
					: configuration.newMetaObject(parameterObject);
			for (int i = 0; i < parameterMappings.size(); i++) {
				ParameterMapping parameterMapping = parameterMappings.get(i);
				if (parameterMapping.getMode() != ParameterMode.OUT) {
					Object value;
					String propertyName = parameterMapping.getProperty();
					PropertyTokenizer prop = new PropertyTokenizer(propertyName);
					if (parameterObject == null) {
						value = null;
					} else if (typeHandlerRegistry
							.hasTypeHandler(parameterObject.getClass())) {
						value = parameterObject;
					} else if (boundSql.hasAdditionalParameter(propertyName)) {
						value = boundSql.getAdditionalParameter(propertyName);
					} else if (propertyName
							.startsWith(ForEachSqlNode.ITEM_PREFIX)
							&& boundSql.hasAdditionalParameter(prop.getName())) {
						value = boundSql.getAdditionalParameter(prop.getName());
						if (value != null) {
							value = configuration.newMetaObject(value)
									.getValue(
											propertyName.substring(prop
													.getName().length()));
						}
					} else {
						value = metaObject == null ? null : metaObject
								.getValue(propertyName);
					}
					TypeHandler typeHandler = parameterMapping.getTypeHandler();
					if (typeHandler == null) {
						throw new ExecutorException(
								"There was no TypeHandler found for parameter "
										+ propertyName + " of statement "
										+ mappedStatement.getId());
					}
					typeHandler.setParameter(ps, i + 1, value,
							parameterMapping.getJdbcType());
				}
			}
		}
	}

}
